package fr.inserm.u915.organifooyi.controlers;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

import fr.inserm.u915.organifooyi.lang.Cast;
import fr.inserm.u915.organifooyi.lang.Equals;
import fr.inserm.u915.organifooyi.model.AbstractPersistentObject;
import fr.inserm.u915.organifooyi.model.AbstractUpdatableObject;
import fr.inserm.u915.organifooyi.model.Aliquot;
import fr.inserm.u915.organifooyi.model.DataType;
import fr.inserm.u915.organifooyi.model.Extraction;
import fr.inserm.u915.organifooyi.model.History;
import fr.inserm.u915.organifooyi.model.HistoryTable;
import fr.inserm.u915.organifooyi.model.Individual;
import fr.inserm.u915.organifooyi.model.Protocol;
import fr.inserm.u915.organifooyi.model.ReceptionTubes;
import fr.inserm.u915.organifooyi.model.SearchHit;
import fr.inserm.u915.organifooyi.model.Status;
import fr.inserm.u915.organifooyi.model.Tube;
import fr.inserm.u915.organifooyi.model.User;
import fr.inserm.u915.organifooyi.model.UserRole;
import fr.inserm.u915.organifooyi.web.Path;

public class MainHandler extends AbstractHandler
	{
	/** current transaction */
	private EntityTransaction txn=null;
	/** NOW */
	private Date now=new Date();
	public MainHandler()
		{
		}
	
	
	private EntityTransaction getTransaction()
		{
		if(txn==null) txn=getEntityManager().getTransaction();
		return txn;
		}
	
	private static Object oneOrZero(List<?> L)
		{
		if(L.isEmpty()) return null;
		if(L.size()==1) return L.get(0);
		throw new IllegalStateException("list contains more than one item : "+L.get(0)+" and "+L.get(1));
		}
	
	protected Individual getIndividualByIntegralis(String integralis)
		{
		if(integralis==null || integralis.isEmpty()) return null;
		return (Individual)oneOrZero(getEntityManager().
			createNamedQuery("individual.find.by.integralis").
			setParameter("integralis", integralis).
			getResultList()
			);
		}

	
	protected ReceptionTubes getReceptionTubesByCDNumber(String cdNumber)
		{
		if(cdNumber==null || cdNumber.length()==0) return null;
		return (ReceptionTubes)oneOrZero(getEntityManager().
			createNamedQuery("reception.find.by.cdNumber").
			setParameter("cdNumber", cdNumber).
			getResultList()
			);
		}

	protected Protocol getProtocolByTitle(String title)
		{
		if(title==null || title.length()==0) return null;
		return (Protocol)oneOrZero(getEntityManager().
			createNamedQuery("protocol.find.by.title").
			setParameter("title", title).
			getResultList()
			);
		}

	protected User getUserByLogin(String login)
		{
		if(login==null || login.length()==0) return null;
		return (User)oneOrZero(getEntityManager().
			createNamedQuery("user.find.by.login").
			setParameter("login", login).
			getResultList()
			);
		}

	
	@SuppressWarnings("unchecked")
	protected List<Protocol> getProtocolsByType(String protocolType)
		{
		return ( List<Protocol>)getEntityManager().
			createNamedQuery("protocol.find.by.type").
			setParameter("type", protocolType).
			getResultList()
			;
		}

	@SuppressWarnings("unchecked")
	protected List<History> getHistoryByEntity(DataType objectType,Long objectId)
		{
		if(objectType==null || objectId==null) return new ArrayList<History>();
		return ( List<History>)getEntityManager().
				createNamedQuery("history.find.by.type.and.id").
				setParameter("objectType", objectType).
				setParameter("objectId", objectId).
				getResultList()
				;
		}
	
	@SuppressWarnings("unchecked")
	protected List<Aliquot> getAllAliquots()
		{
		return ( List<Aliquot>)getEntityManager().
				createNamedQuery("aliquot.list.all").
				getResultList()
				;
		}
	
	
	protected Extraction getExtractionById(long id)
		{
		return getEntityManager().find(Extraction.class, id);
		}
	
	
	protected Individual getIndividualById(long id)
		{
		return getEntityManager().find(Individual.class, id);
		}
	
	protected Tube getTubeById(long id)
		{
		return getEntityManager().find(Tube.class, id);
		}

	protected Protocol getProtocolById(long id)
		{
		return getEntityManager().find(Protocol.class, id);
		}

	protected User getUserById(long id)
		{
		return getEntityManager().find(User.class, id);
		}

	protected Aliquot getAliquotById(long id)
		{
		return getEntityManager().find(Aliquot.class, id);
		}

	protected ReceptionTubes getReceptionById(long id)
		{
		return getEntityManager().find(ReceptionTubes.class, id);
		}

	
	private boolean fillIndividual(Individual indi)
		{
		boolean success=true;
		indi.setIntegralis(getRequest().getParameter("integralis","").trim().toUpperCase());
		getRequest().setAttribute("individual",indi);
		
		if(!Cast.isA(indi.getIntegralis(),BigInteger.class))
			{
			success=false;
			addErrorMessage("Integralis should match [0-9]+");
			}
		return success;
		}
	
	public User getUser()
		{
		Principal principal=getRequest().getUserPrincipal();
		String login=(principal==null || principal.getName()==null?"anonymous":principal.getName());
		try
			{
			return (User)getEntityManager().
				createNamedQuery("user.find.by.login").
				setParameter("login", login).
				getSingleResult()
				;
			}
		catch (Throwable e)
			{
			e.printStackTrace();
			throw new RuntimeException(e);
			}
		}
	
	private AbstractPersistentObject fillCreation(AbstractPersistentObject o)
		{
		User u=getUser();
		if(u==null) throw new RuntimeException("use is null???");
		o.setCreatedBy(u);
		o.setCreation(this.now);
		if(o instanceof AbstractUpdatableObject)
			{
			AbstractUpdatableObject o2=AbstractUpdatableObject.class.cast(o);
			o2.setModifiedBy(u);
			o2.setModified(this.now);
			}
		return o;
		}
	private AbstractPersistentObject fillUpdate(AbstractUpdatableObject o)
		{
		o.setModifiedBy(getUser());
		o.setModified(this.now);
		return o;
		}
	

	
	protected Extraction findExtractionByLabel(String q)
		{
		if(q==null || q.isEmpty()) return null;
		if(q.matches("[cC][dD]\\d\\d\\d\\d\\d_[A-Za-z]"))
			{
			String tokens[]=q.split("[_]");
			ReceptionTubes r2=getReceptionTubesByCDNumber(tokens[0].toUpperCase());
			if(r2!=null)
				{
				for(Extraction ex:r2.getExtractions())
					{
					if(((Character)tokens[1].toUpperCase().charAt(0)).equals(ex.getSymbol()))
						{
						return ex;
						}
					}
				}
			}
		return null;
		}
	
	protected Aliquot findAliquotByLabel(String q)
		{
		if(q==null || q.isEmpty()) return null;
		if(q.matches("[cC][dD]\\d\\d\\d\\d\\d_[A-Za-z]_[0-9]+"))
			{
			String tokens[]=q.split("[_]");
			Extraction ex=findExtractionByLabel(tokens[0].toUpperCase()+"_"+tokens[1]);
			if(ex==null) return null;
			for(Aliquot a: ex.getAliquots())
				{
				if(String.valueOf(a.getIndex()).equals(tokens[2]))
					{
					return a;
					}
				}
			}
		return null;
		}

	
	private int doBatchPrelevements()  throws IOException,ServletException
			{
			final DataType dataType=DataType.aliquot;
			Set<Aliquot> aliquots=new HashSet<Aliquot>();
			List<History> history=new ArrayList<History>();
			Double prelevVolume= getRequest().getParameter("prelevVolume", Double.class);
			Short trimToZero=getRequest().getParameter("trimVolume", Short.class,(short)0);
			if(prelevVolume==null || prelevVolume<=0)
				{
				addErrorMessage("Bad prelev. volume:"+getRequest().getParameter("prelevVolume"));
				return forwardTo("/WEB-INF/pages/"+dataType+"/batch-operations.jsp");
				}
			
			boolean is_first_time= getRequest().getParameter("_second")==null;
			String texarea=getRequest().getParameter("aliquot_ids", "");
			for(String s:texarea.split("[,;\n\r \t]+"))
				{
				if(s.isEmpty()) continue;
				Aliquot a= findAliquotByLabel(s);
				if(a==null && !is_first_time)
					{
					addErrorMessage("Cannot find aliquot.label=\""+s+"\"");
					continue;
					}
				aliquots.add(a);
				}
			
			if(aliquots.isEmpty() && !is_first_time)
				{
				addErrorMessage("Empty query");
				}
			
			for(Aliquot aliquot:aliquots)
				{
				Double oldVol=aliquot.getVolume();
				if(oldVol==null)
					{
					addErrorMessage(aliquot.getLabel()+" undefined volume");
					continue;
					}
				if(trimToZero!=1 && oldVol.compareTo(prelevVolume)<0)
					{
					addErrorMessage(aliquot.getLabel()+" not enought volume");
					continue;
					}
				Double newVol=Math.max(0.0,oldVol-prelevVolume);
				
				aliquot.setVolume(newVol);
				history.add(new History(
						dataType,
						aliquot.getId(),
						aliquot.getVersion(),
						"volume",
						Double.class.getSimpleName(),
						String.valueOf(oldVol),
						String.valueOf(newVol),
						"batch prelevement : "+prelevVolume
						));
				}
			if(!this.containsErrorMessages())
				{
				EntityTransaction txn=getTransaction();
				try
					{
					txn.begin();
					
					for(History hist:history)
						{
						fillCreation(hist);
						getEntityManager().persist(hist);
						}
	
					for(Aliquot aliquot:aliquots)
						{
						fillUpdate(aliquot);
						getEntityManager().persist(aliquot);
						}
					
					getEntityManager().flush();
					txn.commit();
					addSuccessMessage("OK. Updated : "+aliquots.size()+" "+dataType+"(s)");
					return forwardTo("/db/aliquot/list");
					}
				catch(Throwable err)
					{
					if(txn.isActive()) txn.rollback();
					addMessage(err);
					}
				}
			return forwardTo("/WEB-INF/pages/"+dataType+"/batch-operations.jsp");
			}

	private int doBatchUpdateAliquots() throws IOException,ServletException
		{
		final String available_fields[]=new String[]{"volume","concNanoDrop"};
		final DataType dataType=DataType.aliquot;
		final String batchPage="/WEB-INF/pages/"+dataType+"/batch-operations.jsp";
		String texarea=getRequest().getParameter("aliquot_ids", "");
		Pattern tab=Pattern.compile("[\t]");
		BufferedReader in=new BufferedReader(new StringReader(texarea));
		String line=in.readLine();
		if(line==null)
			{
			addErrorMessage("No Header");
			return forwardTo(batchPage);
			}
		int aliquot_label_column=-1;
		int aliquot_concentration_column=-1;
		int aliquot_volume_column=-1;
		String headers[]=tab.split(line);
		for(int i=0;i< headers.length;++i)
			{
			//addPlainMessage("["+headers[i]+"]");
			if(headers[i].equals("aliquot.label"))
				{
				if(aliquot_label_column!=-1)
					{
					throw new IOException("aliquot_label_column defined twice");
					}
				aliquot_label_column=i;
				}
			else if(headers[i].equals("aliquot.volume"))
				{
				if(aliquot_volume_column!=-1)
					{
					throw new IOException("aliquot_volume_column defined twice");
					}
				aliquot_volume_column=i;
				}
			else if(headers[i].equals("aliquot.concNanoDrop"))
				{
				//addPlainMessage("x"+headers[i]);
				if(aliquot_concentration_column!=-1)
					{
					throw new IOException("aliquot_concentration_column defined twice");
					}
				aliquot_concentration_column=i;
				}
			}
		if(aliquot_label_column==-1)
			{
			addErrorMessage("Undefined column label in "+line);
			}
		
		List<History> history=new ArrayList<History>();
		List<Aliquot> aliquots=new ArrayList<Aliquot>();
		while((line=in.readLine())!=null)
			{
			if(line.trim().isEmpty()) continue;
			String tokens[]=tab.split(line);
			
			if(tokens.length!=headers.length)
				{
				addErrorMessage("not enough columns in "+line);
				continue;
				}
			Aliquot aliquot=findAliquotByLabel(tokens[aliquot_label_column]);
			boolean changed=false;
			if(aliquot==null)
				{
				addErrorMessage("Cannot find aliquot from "+line);
				continue;
				}
			
			for(int fieldidx=0;fieldidx< available_fields.length;++fieldidx)
				{
				int the_columnn=-1;
				switch(fieldidx)
					{
					case 0:the_columnn=aliquot_volume_column;break;
					case 1:the_columnn=aliquot_concentration_column;break;
					default:break;
					}
				
				if(the_columnn!=-1)
					{
					Status status=aliquot.update(available_fields[fieldidx],tokens[the_columnn]);
					if(!status.isIgnorable())
						{
						if(status.isHistory())
							{
							changed=true;
							history.add(status.getHistory());
							}
						else
							{
							addMessage(status.getMessage());
							}
						}
					}
				}
		
			if(changed)
				{
				aliquots.add(aliquot);
				}
			}
		in.close();
		if(this.containsErrorMessages())
			{
			return forwardTo(batchPage);
			}
		
		EntityTransaction txn=getTransaction();
		try
			{
			txn.begin();
			for(History hist:history)
				{
				hist.setLabel("bacth operation");
				fillCreation(hist);
				getEntityManager().persist(hist);
				}

			for(Aliquot aliquot:aliquots)
				{
				fillUpdate(aliquot);
				getEntityManager().persist(aliquot);
				}
			
			getEntityManager().flush();
			txn.commit();
			addSuccessMessage("OK. Updated : "+aliquots.size()+" aliquots(s)");
			if(aliquots.size()==1)
				{
				return forwardTo("/db/aliquot/show/"+aliquots.iterator().next().getId());
				}
		
			return forwardTo("/db/aliquot/list");
			}
		catch(PersistenceException err)
			{
			if(txn.isActive()) txn.rollback();
			addMessage(err);
			throw err;
			}
		//unreachable throw new IllegalStateException("");
		}
	
	
	private int exportAliquots() throws IOException,ServletException
		{
		final DataType dataType=DataType.aliquot;
		Set<Aliquot> aliquots_set=new HashSet<Aliquot>();
		boolean is_first_time= getRequest().getParameter("_second")==null;
		String texarea=getRequest().getParameter("aliquot_ids", "");
		for(String s:texarea.split("[,;\n\r \t]+"))
			{
			if(s.isEmpty()) continue;
			Aliquot a= findAliquotByLabel(s);
			if(a==null && !is_first_time)
				{
				addErrorMessage("Cannot find aliquot.label=\""+s+"\"");
				continue;
				}
			aliquots_set.add(a);
			}
		Collection<Aliquot> aliquots=aliquots_set;
		if(aliquots.isEmpty() && !is_first_time)
			{
			aliquots=getAllAliquots();
			}

		if(!containsErrorMessages())
			{
			getResponse().setContentType("application/vnd.ms-excel");
			getResponse().setHeader("Content-Disposition", "attachment; filename=aliquots.tsv");
			PrintWriter out=getResponse().getWriter();
			out.println("aliquot.label\taliquot.volume\taliquot.concNanoDrop");
			for(Aliquot a:aliquots)
				{
				out.print(a.getLabel());
				out.print('\t');
				out.print(a.getVolume()==null?"":String.valueOf(a.getVolume()));
				out.print('\t');
				out.print(a.getConcNanoDrop()==null?"":String.valueOf(a.getConcNanoDrop()));
				out.println();
				}
			out.flush();
			out.close();
			return 0;
			}
		
		return forwardTo("/WEB-INF/pages/"+dataType+"/batch-operations.jsp");
		}
	
	private int doSearch() throws ServletException,IOException
		{
		String query=getRequest().getParameter("q","").trim();
		if(query.isEmpty()) return forwardTo("/WEB-INF/pages/welcome.jsp");
		List<SearchHit> allHits=new ArrayList<SearchHit>();
		for(String q:query.split("[ \t\n\r]+"))
			{
			List<SearchHit> hits=new ArrayList<SearchHit>();
			Individual i1=getIndividualByIntegralis(q.toUpperCase());
			if(i1!=null) hits.add(new SearchHit(DataType.individual,i1.getId(),i1.getIntegralis()));
			ReceptionTubes r1=getReceptionTubesByCDNumber(q.toUpperCase());
			if(r1!=null) hits.add(new SearchHit(DataType.reception,r1.getId(),r1.getCdNumber()));
			Extraction ex1=findExtractionByLabel(q.toUpperCase());
			if(ex1!=null)
				{
				hits.add(new SearchHit(DataType.extraction,ex1.getId(),ex1.getLabel()));
				}
			Aliquot a1= findAliquotByLabel(q.toLowerCase());
			if(a1!=null)
				{
				hits.add(new SearchHit(DataType.aliquot,a1.getId(),a1.getLabel()));
				}
			
			if(hits.isEmpty())
				{
				addWarningMessage("Not Hit found for "+q);
				}
			allHits.addAll(hits);
			}
		
		if(allHits.isEmpty())
			{
			return forwardTo("/WEB-INF/pages/welcome.jsp");
			}
		else if(allHits.size()==1)
			{
			return forwardTo(allHits.get(0).getForwardTo());
			}
		else
			{
			getRequest().setAttribute("searchHits", allHits);
			return forwardTo("/WEB-INF/pages/search-results.jsp");
			}
			
		}
	
	
	
	@Override
	public int service() throws IOException,ServletException
		{
		Path path=getPath();
		
		if(isUserCanWrite())
			{
			getRequest().setAttribute("userHasWritePrivileges", true);
			}
		
		try
			{
			if(path.size()==1 && path.endsWith("logout"))
				{
				HttpSession session=getRequest().getSession(false);
				if(session!=null)
					{
					addWarningMessage("logged-off");
					session.invalidate();
					}
				return forwardTo("/WEB-INF/pages/welcome.jsp");
				}
			if(path.size()==1 && path.endsWith("search"))
				{
				return doSearch();
				}
			else if(path.isA(0, DataType.class))
				{
				DataType dataType=path.get(0, DataType.class);
				if(dataType==null) throw new ServletException("?"+path.get(0));	
				/* BATCH UPDATE */
				if(path.size()==2 && path.endsWith("batch-operation") && dataType==DataType.aliquot)
					{
					boolean is_first_time= getRequest().getParameter("_second")==null;
					if(is_first_time)
						{
						return forwardTo("/WEB-INF/pages/"+dataType+"/batch-operations.jsp"); 
						}
					
					String action= getRequest().getParameter("action","export");
					/* read tab-delimited file and update data*/
					if(action.equals("maj"))
						{
						return doBatchUpdateAliquots();
						}
					else if(action.equals("prelevement"))
						{
						return doBatchPrelevements();
						}
					else
						{
						return exportAliquots();
						}
					}
				else if(path.size()==2
						&& path.endsWith("batch-update")
						&& dataType==DataType.aliquot)
					{
					boolean is_first_time= getRequest().getParameter("_second")==null;
					List<Aliquot> aliquots=new ArrayList<Aliquot>();
					if(getRequest().getParameterValues("aliquot_id")!=null)
					{
					for(String v:getRequest().getParameterValues("aliquot_id"))
						{
						Long aliquot_id=Cast.cast(v, Long.class);
						if(aliquot_id==null || aliquot_id<1L) continue;
						Aliquot aliquot = getAliquotById(aliquot_id);
						if(aliquot==null) continue;
						aliquots.add(aliquot);
						}
					}
					if(aliquots.isEmpty())
						{
						addWarningMessage("No aliquot was selected");
						return forwardTo("/WEB-INF/pages/"+dataType+"/list");
						}
					
					getRequest().setAttribute("aliquots",aliquots);
					if(is_first_time)
						{
						return forwardTo("/WEB-INF/pages/"+dataType+"/batch-update.jsp");
						}
					List<History> history=new ArrayList<History>();
					boolean ok=true;
					for(Aliquot aliquot:aliquots)
						{
						/* nano drop */
						String v=getRequest().getParameter("concNanoDrop["+aliquot.getId()+"]","").trim();
						
						if(v!=null)
							{
							Double dblValue=Cast.cast(v, Double.class);
							if((dblValue==null && !v.isEmpty()) || (dblValue!=null && dblValue<0 ))
								{
								addErrorMessage("BAD conc NanoDrop ("+v+") for "+aliquot.getLabel());
								ok=false;
								}
							else
								{
								if(!Equals.equals(dblValue, aliquot.getConcNanoDrop()))
									{
									history.add(new History(
											dataType,
											aliquot.getId(),
											aliquot.getVersion(),
											"concNanoDrop",
											Double.class.getSimpleName(),
											String.valueOf(aliquot.getConcNanoDrop()),
											String.valueOf(dblValue),
											null
											));
									}
								aliquot.setConcNanoDrop(dblValue);
								}		
							}
						/* ratio260280 */
						v=getRequest().getParameter("ratio260280["+aliquot.getId()+"]","").trim();
						if(v!=null)
							{
							Double dblValue=Cast.cast(v, Double.class);
							if((dblValue==null && !v.isEmpty()) || (dblValue!=null && dblValue<0 ))
								{
								addErrorMessage("BAD ratio 260/280 ("+v+") for "+aliquot.getLabel());
								ok=false;
								}
							else
								{
								if(!Equals.equals(dblValue, aliquot.getRatio260280()))
									{
									history.add(new History(
											dataType,
											aliquot.getId(),
											aliquot.getVersion(),
											"ratio260280",
											Double.class.getSimpleName(),
											String.valueOf(aliquot.getRatio260280()),
											String.valueOf(dblValue),
											null
											));
									}
								aliquot.setRatio260280(dblValue);
								}		
							}
						/* ratio260230 */
						v=getRequest().getParameter("ratio260230["+aliquot.getId()+"]","").trim();
						if(v!=null)
							{
							Double dblValue=Cast.cast(v, Double.class);
							if((dblValue==null && !v.isEmpty()) || (dblValue!=null && dblValue<0 ))
								{
								addErrorMessage("BAD ratio 260/230 ("+v+") for "+aliquot.getLabel());
								ok=false;
								}
							else
								{
								if(!Equals.equals(dblValue, aliquot.getRatio260230()))
									{
									history.add(new History(
											dataType,
											aliquot.getId(),
											aliquot.getVersion(),
											"ratio260230",
											Double.class.getSimpleName(),
											String.valueOf(aliquot.getRatio260280()),
											String.valueOf(dblValue),
											null
											));
									}
								aliquot.setRatio260230(dblValue);
								}		
							}
						/* concPicoGreen */
						v=getRequest().getParameter("concPicoGreen["+aliquot.getId()+"]","").trim();
						if(v!=null)
							{
							Double dblValue=Cast.cast(v, Double.class);
							if((dblValue==null && !v.isEmpty()) || (dblValue!=null && dblValue<0 ))
								{
								addErrorMessage("BAD conc PicoGreen ("+v+") for "+aliquot.getLabel());
								ok=false;
								}
							else
								{
								if(!Equals.equals(dblValue, aliquot.getConcPicoGreen()))
									{
									history.add(new History(
											dataType,
											aliquot.getId(),
											aliquot.getVersion(),
											"concPicoGreen",
											Double.class.getSimpleName(),
											String.valueOf(aliquot.getConcPicoGreen()),
											String.valueOf(dblValue),
											null
											));
									}
								aliquot.setConcPicoGreen(dblValue);
								}		
							}
						/* gel */
						v=getRequest().getParameter("gel["+aliquot.getId()+"]","").trim();
						
						if(v!=null)
							{
							Short shrtValue=Cast.cast(v, Short.class);;
							
							if((shrtValue==null && !v.isEmpty()) || (shrtValue!=null && !(shrtValue==(short)1 || shrtValue==(short)0) ))
								{
								addErrorMessage("bad gel ("+v+") for "+aliquot.getLabel());
								ok=false;
								}
							else
								{
								if(!Equals.equals(shrtValue, aliquot.getGel()))
									{
									history.add(new History(
											dataType,
											aliquot.getId(),
											aliquot.getVersion(),
											"gel",
											Short.class.getSimpleName(),
											String.valueOf(aliquot.getGel()),
											String.valueOf(shrtValue),
											null
											));
									}
								aliquot.setGel(shrtValue);
								}
							}
						/* volume */
						v=getRequest().getParameter("volume["+aliquot.getId()+"]","").trim();
						if(v!=null)
							{
							Double dblValue=Cast.cast(v, Double.class);
							if((dblValue==null && !v.isEmpty()) || (dblValue!=null && dblValue<0 ))
								{
								addErrorMessage("BAD volume ("+v+") for "+aliquot.getLabel());
								ok=false;
								}
							else
								{
								if(!Equals.equals(dblValue, aliquot.getVolume()))
									{
									history.add(new History(
											dataType,
											aliquot.getId(),
											aliquot.getVersion(),
											"volume",
											Double.class.getSimpleName(),
											String.valueOf(aliquot.getVolume()),
											String.valueOf(dblValue),
											null
											));
									}
								aliquot.setVolume(dblValue);
								}		
							}
						}
					if(!ok)
						{
						return forwardTo("/WEB-INF/pages/"+dataType+"/batch-update.jsp");
						}
					EntityTransaction txn=getTransaction();
					try
						{
						txn.begin();
						Set<Long> to_update=new HashSet<Long>();
						for(History hist:history)
							{
							to_update.add(hist.getObjectId());
							fillCreation(hist);
							getEntityManager().persist(hist);
							}

						for(Aliquot aliquot:aliquots)
							{
							if(!to_update.contains(aliquot.getId())) continue;
							fillUpdate(aliquot);
							getEntityManager().persist(aliquot);
							}
						
						getEntityManager().flush();
						txn.commit();
						addSuccessMessage("OK. Updated : "+to_update.size()+" "+dataType+"(s)");

						if(aliquots.size()==1)
							{
							return forwardTo("/db/aliquot/show/"+aliquots.get(0).getId());
							}
						
						return forwardTo("/db/aliquot/list");
						}
					catch(PersistenceException err)
						{
						if(txn.isActive()) txn.rollback();
						addMessage(err);
						}
					return forwardTo("/WEB-INF/pages/"+dataType+"/batch-update.jsp");
					}
				/* LIST *********************************************************************************/
				else if(path.size()==2 && path.endsWith("list"))
					{
					switch(dataType)
						{
						case user:
							{
							getRequest().setAttribute(
									"users",
									getEntityManager().
									createNamedQuery("user.list.all").getResultList());
							break;
							}
						case individual:
							{
							getRequest().setAttribute(
									"individuals",
									getEntityManager().
									createNamedQuery("individual.list.all").getResultList());
							break;
							}
						case reception:
							{
							getRequest().setAttribute(
									"receptions",
									getEntityManager().
									createNamedQuery("reception.list.all").getResultList());
							break;
							}
						case extraction:
							{
							getRequest().setAttribute(
									"extractions",
									getEntityManager().
									createNamedQuery("extraction.list.all").getResultList());
							break;
							}
						case protocol:
							{
							getRequest().setAttribute(
									"protocols",
									getEntityManager().
									createNamedQuery("protocol.list.all").getResultList());
							break;
							}
						case aliquot:
							{
							getRequest().setAttribute(
									"aliquots",
									getAllAliquots());
							break;
							}
						}
					return forwardTo("/WEB-INF/pages/"+dataType+"/list.jsp");
					}
				/* CREATE *********************************************************************************/
				else if(
						path.size()==2 &&
						(path.endsWith("create") || path.endsWith("validate")) && 
						verifyUserCanWrite()
						)
					{
					boolean is_first_time =path.endsWith("create");
					
					switch(dataType)
						{
						/* USER *******************************************************************/ 
						case user:
							{
							if(!isUserAdmin())
								{
								addErrorMessage("You don't have the privilege to do this action ");
								return forwardTo("/WEB-INF/pages/"+dataType+"/list.jsp");
								}
							String login=getRequest().getParameter("login","").trim();
							if(login.isEmpty() && !is_first_time)
								{
								addErrorMessage("Empty login");
								return forwardTo("/WEB-INF/pages/"+dataType+"/create.jsp");
								}
							String pass1=getRequest().getParameter("password1","").trim();
							String pass2=getRequest().getParameter("password2","").trim();
							if(!pass1.equals(pass2) && !is_first_time)
								{
								addErrorMessage("Not the same password");
								return forwardTo("/WEB-INF/pages/"+dataType+"/create.jsp");
								}
							if(!is_first_time)
								{
								EntityTransaction txn=getTransaction();
								UserRole role=null;
								try
									{
									txn.begin();
								
									User user=getUserByLogin(login);
									if(user==null)
										{
										user=new User();
										user.setLogin(login);
										user.setName(login);
										
										role=new UserRole();
										role.setAuthority("ROLE_USER");
										role.setLogin(login);
										role.setUser(user);
										user.getRoles().add(role);
										}
									MessageDigest md5Helper=MessageDigest.getInstance("MD5");
									byte secret[]=new String(login+":rotavirus:"+pass1).getBytes();
									md5Helper.update(secret,0,secret.length);
									secret=md5Helper.digest();
									
									String hash=new org.apache.catalina.util.MD5Encoder().encode(secret);
									if(hash==null)
										{
										throw new NullPointerException("null");
										}
									user.setMd5password(new String(hash));
									
									getEntityManager().persist(user);
									if(role!=null) getEntityManager().persist(role);
									getEntityManager().flush();
									txn.commit();
									
									addSuccessMessage("OK. User : "+user.getLogin());
									return forwardTo("/db/user/show/"+user.getId());
									
									}
								catch(PersistenceException err)
									{
									if(txn.isActive()) txn.rollback();
									addMessage(err);
									}
								}
							break;
							}
					
						/* INDIVIDUAL *******************************************************************/ 
						case individual:
							{
							Individual indi=new Individual();
							boolean ok=fillIndividual(indi);
							getRequest().setAttribute("individual", indi);
							EntityTransaction txn=getTransaction();
							try
								{
								txn.begin();
								Individual other=getIndividualByIntegralis(indi.getIntegralis());
								
								if(other!=null)
										{
										addErrorMessage("Duplicate Integralis ID: "+indi.getIntegralis());
										ok=false;
										}
								if(ok && !is_first_time)
									{
									fillCreation(indi);
									getEntityManager().persist(indi);
									getEntityManager().flush();
									txn.commit();
									
									addSuccessMessage("OK. Created a new Individual : "+indi.getId());
									return forwardTo("/db/individual/show/"+indi.getId());
									}
								}
							catch(PersistenceException err)
								{
								if(txn.isActive()) txn.rollback();
								addMessage(err);
								}
							break;
							}
						/* RECEPTION *******************************************************************/ 
						case reception:
							{
							boolean ok=true;
							Long individualid=getRequest().getParameter("individual_id",Long.class);
							if(individualid==null) throw new ServletException("individual_id missing "+getRequest().getParameter("individual_id"));
							Individual individual=getIndividualById(individualid);
							
							if(individual==null) throw new ServletException("unknown individual "+individualid);
							ReceptionTubes reception=new ReceptionTubes();
							getRequest().setAttribute("reception", reception);
							reception.setIndividual(individual);
							reception.setCommentaire(getRequest().getParameter("commentaire", ""));
							reception.setCdNumber(getRequest().getParameter("cdNumber", ""));
							reception.setDateReception(getRequest().getParameter("dateReception", new java.sql.Date(System.currentTimeMillis())).toString());
							if(!reception.getDateReception().matches("20\\d\\d\\-[01]\\d\\-[0123]\\d") && !is_first_time)
								{
								ok=false;
								addErrorMessage("Date of Reception not a YYYY-MM-DD");
								}
							
							String errorCD=null;
							if((errorCD=reception.isValidCDNumber())!=null && !is_first_time)
								{
								ok=false;
								addErrorMessage(errorCD);
								}
							if(getReceptionTubesByCDNumber(reception.getCdNumber())!=null)
								{
								ok=false;
								addErrorMessage("Duplicate CD-Number");
								}
							
							
							
							reception.setCountInit(getRequest().getParameter("countInit",Short.class));
							if(reception.getCountInit()==null || reception.getCountInit()<=0 && !is_first_time)
								{
								ok=false;
								addErrorMessage("Bad Count Init");
								}
							if(!is_first_time && ok)
								{
								EntityTransaction txn=getTransaction();
								try
									{
									txn.begin();
								
									fillCreation(reception);
									
									
									for(short t=0;t< reception.getCountInit();++t)
										{
										Tube tube=new Tube();
										tube.setTubeIndex((short)(t+1));
										tube.setReceptionTubes(reception);
										fillCreation(tube);
										reception.getTubes().add(tube);
										}
									individual.getReceptionTubes().add(reception);
									getEntityManager().persist(reception);
									getEntityManager().flush();
									txn.commit();
									
									addSuccessMessage("OK. Created a Reception for Individual : "+individual.getIntegralis());
									return forwardTo("/db/individual/show/"+individual.getId());
									
									}
								catch(PersistenceException err)
									{
									if(txn.isActive()) txn.rollback();
									addMessage(err);
									}
								}
							break;
							}
						/* EXTRACTION *******************************************************************/ 
						case extraction:
							{
							boolean ok=true;
							
							
							
							
							Long reception_id=getRequest().getParameter("reception_id",Long.class);
							if(reception_id==null) throw new ServletException("reception_id missing "+getRequest().getParameter("individual_id"));
							ReceptionTubes reception=getReceptionById(reception_id);
							
							if(reception==null) throw new ServletException("unknown reception "+reception_id);
							
							int maxSymbol='A';
							for(Extraction e: reception.getExtractions())
								{
								if(e.getSymbol()==null ) continue;
								int c=Character.toUpperCase(e.getSymbol());
								if(maxSymbol<=c) maxSymbol=c+1;
								}
							
							
							Extraction extraction=new Extraction();
							getRequest().setAttribute("extraction", extraction);
							getRequest().setAttribute("protocols", getProtocolsByType("extraction"));
							
							extraction.setReception(reception);
							extraction.setSymbol(getRequest().getParameter("symbol",Character.class));
							if(extraction.getSymbol()==null) extraction.setSymbol((char)maxSymbol);
							
							for(String s:getRequest().getParameterNamesAsSet())
								{
								if(!s.matches("tube_[\\d]+")) continue;
								Tube t= getTubeById(Long.parseLong(s.substring(5)));
								if(t==null) throw new ServletException("Unkown tube");
								if(t.getExtraction()!=null) throw new ServletException("tube already associated to an extraction");
								extraction.getTubes().add(t);
								//t.setExtraction(extraction); non, a faire plus tard
								}
							
							if(extraction.getTubes().isEmpty())
								{
								if(!is_first_time) addErrorMessage("No tube was selected");
								ok=false;
								}
							
							for(Extraction e: reception.getExtractions())
								{
								if(extraction.getSymbol()==e.getSymbol())
									{
									addErrorMessage("extraction symbol defined twice");
									ok=false;
									}
								}
							
							
							Long protocol_id=getRequest().getParameter("protocol_id", Long.class);
							if(protocol_id==null)
								{
								if(!is_first_time) addErrorMessage("undefined protocol-id");
								ok=false;
								}
							else
								{
								Protocol protocol=getProtocolById(protocol_id);
								if(protocol==null)
									{
									addErrorMessage("undefined protocol-id "+protocol_id);
									ok=false;
									}
								else if(!protocol.getType().equals("extraction"))
									{
									addErrorMessage("not an extraction protocol");
									ok=false;
									}
								else
									{
									extraction.setProtocol(protocol);
									}
								}
							
							
							if(!is_first_time && ok)
								{
								EntityTransaction txn=getTransaction();
								try
									{
									txn.begin();
								
									fillCreation(extraction);
									
									
									for(Tube t:extraction.getTubes())
										{
										t.setExtraction(extraction);
										fillUpdate(t);
										}
									reception.getExtractions().add(extraction);
									
									getEntityManager().persist(reception);
									getEntityManager().persist(extraction);
									getEntityManager().flush();
									txn.commit();
									
									addSuccessMessage("OK. Created a Extraction for Reception : "+reception.getCdNumber());
									return forwardTo("/db/reception/show/"+reception.getId());
									}
								catch(PersistenceException err)
									{
									if(txn.isActive()) txn.rollback();
									addMessage(err);
									}
								}
							
							break;
							}
						/* PROTOCOL *******************************************************************/ 
						case protocol:
							{
							boolean ok=true;
							Protocol protocol=new Protocol();
							getRequest().setAttribute("protocol", protocol);
							protocol.setContent(getRequest().getParameter("content", "").trim());
							protocol.setTitle(getRequest().getParameter("title", "").trim());
							protocol.setType(getRequest().getParameter("type", "").trim());
							
							
							
							EntityTransaction txn=getTransaction();
							try
								{
								txn.begin();
								
								if( !is_first_time)
									{
									Protocol other=getProtocolByTitle(protocol.getTitle());
									if(other!=null )
										{
										addErrorMessage("Duplicate protocol Title: "+other.getTitle());
										ok=false;
										}
									if(protocol.getTitle().isEmpty() || protocol.getTitle().length()>=50)
										{
										addErrorMessage("Bad length for title: "+protocol.getTitle());
										ok=false;
										}
									if(protocol.getType().isEmpty() ||
										!(protocol.getType().equals("extraction") ||  protocol.getType().equals("purification")))
										{
										addErrorMessage("Bad protocol type: "+protocol.getType());
										ok=false;
										}
									if(protocol.getContent().isEmpty())
										{
										addErrorMessage("Content for protocol is Empty");
										ok=false;
										}
									}
								
								
								if(ok && !is_first_time)
									{
									fillCreation(protocol);
									getEntityManager().persist(protocol);
									getEntityManager().flush();
									txn.commit();
									
									addSuccessMessage("OK. Created a new Protocol : "+protocol.getId());
									return forwardTo("/db/protocol/show/"+protocol.getId());
									}
								}
							catch(PersistenceException err)
								{
								if(txn.isActive()) txn.rollback();
								addMessage(err);
								}
							break;
							}
						case aliquot:
							{
							boolean ok=true;
							
							Long extraction_id=getRequest().getParameter("extraction_id",Long.class);
							if(extraction_id==null) throw new ServletException("extraction_id missing "+getRequest().getParameter("extraction_id"));
							Extraction extraction=getExtractionById(extraction_id);
							
							if(extraction==null) throw new ServletException("unknown extraction "+extraction);
							
							int maxIndex=1;
							for(Aliquot e: extraction.getAliquots())
								{
								if(e.getIndex()==null ) continue;
								int idx=e.getIndex();
								if(maxIndex<=idx) maxIndex=idx+1;
								}
							
							Aliquot aliquot=new Aliquot();
							getRequest().setAttribute("aliquot", aliquot);
							
							/* nano drop */
							String v=getRequest().getParameter("concNanoDrop","").trim();
							Double dblValue=null;
							if(v!=null)
								{
								if(!is_first_time && !v.isEmpty() &&
									((dblValue=Cast.cast(v, Double.class))==null ||
									dblValue<0 )
									)
									{
									addErrorMessage("BAD conc NanoDrop");
									ok=false;
									}
								else
									{
									aliquot.setConcNanoDrop(Cast.cast(v, Double.class));
									}		
								}
							/* ratio260280 */
							v=getRequest().getParameter("ratio260280","").trim();
							dblValue=null;
							if(v!=null)
								{
								if(!is_first_time && !v.isEmpty() &&
									((dblValue=Cast.cast(v, Double.class))==null ||
									dblValue<0 )
									)
									{
									addErrorMessage("BAD ratio 260/280");
									ok=false;
									}
								else
									{
									aliquot.setRatio260280(Cast.cast(v, Double.class));
									}		
								}
							/* ratio260230 */
							v=getRequest().getParameter("ratio260230","").trim();
							dblValue=null;
							if(v!=null)
								{
								if(!is_first_time && !v.isEmpty() &&
									((dblValue=Cast.cast(v, Double.class))==null ||
									dblValue<0 )
									)
									{
									addErrorMessage("BAD ratio 260/230");
									ok=false;
									}
								else
									{
									aliquot.setRatio260230(Cast.cast(v, Double.class));
									}
								}
							/* concPicoGreen */
							v=getRequest().getParameter("concPicoGreen","").trim();
							dblValue=null;
							if(v!=null)
								{
								if(!is_first_time && !v.isEmpty() &&
									((dblValue=Cast.cast(v, Double.class))==null ||
									dblValue<0 )
									)
									{
									addErrorMessage("BAD conc PicoGreen");
									ok=false;
									}
								else
									{
									aliquot.setConcPicoGreen(Cast.cast(v, Double.class));
									}
								}
							/* gel */
							v=getRequest().getParameter("gel","").trim();
							Short shrtValue=null;
							if(v!=null)
								{
								if(!is_first_time && !v.isEmpty() &&
									((shrtValue=Cast.cast(v, Short.class))==null ||
									!(shrtValue==0 || shrtValue==1))
									)
									{
									addErrorMessage("BAD gel");
									ok=false;
									}
								else
									{
									aliquot.setGel(Cast.cast(v, Short.class));
									}
								}
							/* volume */
							v=getRequest().getParameter("volume","").trim();
							dblValue=null;
							if(v!=null)
								{
								if(!is_first_time && !v.isEmpty() &&
									((dblValue=Cast.cast(v, Double.class))==null ||
									dblValue<0 )
									)
									{
									addErrorMessage("BAD Volume");
									ok=false;
									}
								else
									{
									aliquot.setVolume(Cast.cast(v, Double.class));
									}
								}

							
							aliquot.setExtraction(extraction);
							aliquot.setIndex(getRequest().getParameter("index",Integer.class));
							if(is_first_time )
								{
								aliquot.setIndex(maxIndex);
								}
							else
								{
								if(aliquot.getIndex()==null || aliquot.getIndex()<1)
									{
									ok=false;
									addErrorMessage("Bad index");
									}
								else
									{
									for(Aliquot a:extraction.getAliquots())
										{
										if(a.getIndex().equals(aliquot.getIndex()))
											{
											ok=false;
											addErrorMessage("Duplicate index.");
											}
										}
									}
								}
							
							if(!is_first_time && ok)
								{
								fillCreation(aliquot);
								
								EntityTransaction txn=getTransaction();
								
								
								try
									{
									
									txn.begin();
									extraction.getAliquots().add(aliquot);
									
									getEntityManager().persist(aliquot);
									getEntityManager().persist(extraction);
									getEntityManager().flush();
									txn.commit();
									
									addSuccessMessage("OK. Created an Aliquot for Extraction : "+extraction.getLabel());
									return forwardTo("/db/extraction/show/"+extraction.getId());
									}
								catch(PersistenceException err)
									{
									if(txn.isActive()) txn.rollback();
									addMessage(err);
									}
								}
							
							break;
							
							}
						}
					return forwardTo("/WEB-INF/pages/"+dataType+"/create.jsp");
					}
				else if(path.size()==3 && path.get(1).equals("show") && path.isA(2, Long.class))
					{
					List<History> history=null;
					Long id=path.get(2, Long.class);
					Object object=null;
					switch(dataType)
						{
						case individual:
							{
							object=getIndividualById(id);
							break;
							}
						case extraction:
							{
							object=getExtractionById(id);
							break;
							}
						case reception:
							{
							object=getReceptionById(id);
							break;
							}
						case protocol:
							{
							object=getProtocolById(id);
							break;
							}
						case user:
							{
							object=getUserById(id);
							break;
							}
						case aliquot:
							{
							object=getAliquotById(id);
							history=getHistoryByEntity(dataType,id);
							break;
							}
						}
					if(object==null)
						{
						addErrorMessage("Cannot find "+dataType+" ID."+id);
						return forwardTo("/WEB-INF/pages/"+dataType+"/list.jsp");
						}
					if(history!=null)
						{
						getRequest().setAttribute("historyList", history);
						getRequest().setAttribute("historyTable", new HistoryTable(history));
						}
					getRequest().setAttribute(
							dataType.name(),
							object);
					return forwardTo("/WEB-INF/pages/"+dataType+"/show.jsp");
					}
				addErrorMessage("Cannot handle this request:"+path);
				return forwardTo("/WEB-INF/pages/welcome.jsp");
					
				}
			else if(path.size()==1 && path.endsWith("index"))
				{
				forwardTo("/WEB-INF/pages/welcome.jsp");
				}
			else
				{
				addWarningMessage("Page not found "+path);
				forwardTo("/WEB-INF/pages/welcome.jsp");
				}
			}
		
		catch(IOException err)
			{
			throw err;
			}
		catch(ServletException err)
			{
			throw err;
			}
		catch(Throwable err)
			{
			throw new ServletException(err);
			}
		finally
			{
			if(this.txn!=null)
				{
				this.txn=null;
				}
			}
		return 0;
		}
	@Override
	public void close()
		{
		if(this.txn!=null)
			{
			this.txn=null;
			}
		super.close();
		}
	}
